//#define MatlabDebugCubicFit
//#define DebugCircleOfPixels
//#define RibbonLum3
//#define DebugDeserialization
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Security.Permissions;
using System.ComponentModel;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Data;
using System.Data.Odbc;
using NumericalRecipes;
using FftLib;
using BmgNet;
using StaticUtility;

namespace ObjectLib
{
	
	/// <summary>
	/// [Serializable]
	/// Data for a single ObjectParser project.
	/// </summary>
	[Serializable]
    [XmlRoot("ObjectParserData", Namespace="http://www.cps.utexas.edu/Data/ObjectParser/Xml/ObjectParserData_Schema_1_1.xsd")]
	public class ObjectParserData : ISerializable,  IDeserializationCallback, IXmlSerializable
	{
#if DebugDeserialization
        public static string DbgMsg
        {
            set
            {
                CurrentlyActive.OutputConsoleMessage(value);
            }
        }
#endif
        [NonSerialized]
        public SerializationType LastSerializationType = SerializationType.Binary;
		[NonSerialized]
		public static Random Rand;
        [NonSerialized]
		public static IFormParse CurrentlyActive=null;
		[NonSerialized]
		private static EngMATLib.EngMATAccess matlab;
		public static EngMATLib.EngMATAccess Matlab
		{
			get
			{
				if(matlab==null || !matlab.Active)
					matlab = new EngMATLib.EngMATAccess();
				return matlab;
			}
		}
		static ObjectParserData()
		{
			Rand = new Random();
		}
		/// <summary>
		/// Every time the ObjectParserData file is serialized, this number is incremented.  This makes it possible to
		/// determine whether the database must be updated by comparing this number with the value in the database.
		/// </summary>
		public ulong SerializationCt=0ul;
		/// <summary>
		/// Dimensions of the image.
		/// </summary>
		public int ImageWidth=-1, ImageHeight=-1;
		/// <summary>
		/// Data from image categorization.
		/// </summary>
		public ImageCategorizationData ImageCat;
		public string SearchText;
		public float Fstop;
		public float ShutterRate;
		public float IsoFilmSpeed;
		public float PixelFactor
		{
			get
			{
				return 1000.0f*Fstop*Fstop*ShutterRate/IsoFilmSpeed;
			}
		}
		public string CatText
		{
			get
			{
				return ImageCat.OutputTextWithoutCategory + SearchText;
			}
		}
		/// <summary>
		/// Projname associated with this ObjectParserData object.
		/// </summary>
		public string Projname;
		/// <summary>
		/// Returns Projname without the .ObjParser extension or the path information.
		/// </summary>
		public string ShortProjname
		{
			get
			{
				int first=0;
				int last=Projname.Length-1;
				if(Projname.ToUpper().EndsWith(".OBJPARSER"))
					last -= 10;
				int ct=last;
				while(--ct >= 0)
				{
					if(Projname[ct]=='/' || Projname[ct]=='\\')
					{
						first=ct+1;
						break;
					}
				}
				return Projname.Substring(first, last-first+1);
			}
		}
		/// <summary>
		/// Imagename associated with this ObjectParserData object.
		/// </summary>
		public string Imagename;
		/// <summary>
		/// If the display image is same, this is null.  Otherwise, this is the different name.
		/// </summary>
		public string DisplayImagename;
		/// <summary>
		/// A description of the location in which the image was captured.
		/// </summary>
		public string Location;
		/// <summary>
		/// The GPS coordinates.
		/// </summary>
		public string Gps;
		/// <summary>
		/// The date and time of capture.
		/// </summary>
		public DateTime CapturedAt;
		/// <summary>
		/// Color of the current object.
		/// </summary>
		public Color ObjCurrColor;
		/// <summary>
		/// Alpha and color of within-category objects.
		/// </summary>
		public Color ObjOtherColor;
		/// <summary>
		/// Alpha and color of non-category objects within pixel exclusive scope.
		/// </summary>
		public Color ObjNonColor;
		/// <summary>
		/// Polygon drawing colors.
		/// </summary>
		public Color PolyLineColor;
		/// <summary>
		/// An array of circles to display on the screen.
		/// </summary>
		public Circle[] Circles;
		/// <summary>
		/// The index of the active circle.
		/// </summary>
		public int ActiveCircleInd;
		/// <summary>
		/// Gets the currently active circle.
		/// </summary>
		public Circle ActiveCircle
		{
			get
			{
				if(Circles==null || Circles.Length==0 || ActiveCircleInd<0)
					return null;
				if(ActiveCircleInd>=Circles.Length)
					ActiveCircleInd=0;
				return Circles[ActiveCircleInd];
			}
		}
		/// <summary>
		/// A standing count of the number of times an object has been created.
		/// </summary>
		public int ObjectCt;
		/// <summary>
		/// The root category containing all objects.
		/// </summary>
		public CategoryNode RootCategory;
		/// <summary>
		/// The active category.
		/// </summary>
		private CategoryNode activeCategory;
		/// <summary>
		/// Gets the currently active category.
		/// </summary>
		public CategoryNode ActiveCategory
		{
			get
			{
				return activeCategory;
			}
			set
			{
				activeCategory = value;
			}
		}		
		/// <summary>
		/// The currently active PolygonF or ParsedObject or CategoryNode (the most specific object is given if one is active).
		/// </summary>
		public object ActiveThing
		{
			get
			{
				if(ActiveContour==null)
				{
					if(ActivePoly==null)
					{
						if(ActiveObject==null)
						{
							return ActiveCategory;
						}
						else return ActiveObject;
					}
					else return ActivePoly;
				}
				else return ActiveContour;
			}
			set
			{
				if(value.GetType()==typeof(ContourF))
				{
					ActiveContour = (ContourF)value;
				}
				else if(value.GetType()==typeof(PolygonF))
				{
					ActivePoly = (PolygonF)value;
				}
				else if(value.GetType()==typeof(ParsedObject))
				{
					ActiveObject = (ParsedObject)value;
				}
				else if(value.GetType()==typeof(CategoryNode))
				{
					ActiveCategory = (CategoryNode)value;
				}
			}
		}
		/// <summary>
		/// The currently active ParsedObject.  Returns null if no ParsedObject is currently active.
		/// </summary>
		public ParsedObject ActiveObject
		{
			get
			{
				return this.ActiveCategory.ActiveObject;
			}
			set
			{
				ParsedObject od = (ParsedObject)value;
				ActiveCategory = od.Category;
				od.Category.ActiveObject = od;
			}
		}
		/// <summary>
		/// The currently active PolygonF.  Returns null if no PolygonF is currently active.
		/// </summary>
		public PolygonF ActivePoly
		{
			get
			{
				if(this.ActiveObject==null) return null;
				else return this.ActiveObject.ActivePoly;
			}
			set
			{
				PolygonF pf = (PolygonF)value;
				ActiveObject = pf.Owner;
				pf.Owner.ActivePoly = pf;
			}
		}
		private static CategoryNode _cnTemp;
		public ContourF ActiveContour
		{
			get
			{
				_cnTemp = this.ActiveCategory;
				if(_cnTemp==null)
					return null;
				return _cnTemp.ActiveContour;
			}
			set
			{
				ContourF con = (ContourF)value;
				ActivePoly = con.PolyOwner;
				con.PolyOwner.ActiveContour = value;
			}
		}
		/// <summary>
		/// Initializes a project without an image or project name.
		/// </summary>
		public ObjectParserData()
		{
			Projname = "";
			Imagename = "";
			DisplayImagename = null;
			Init2();
			if(Rand==null)
				Rand = new Random();
			Circles = new Circle[0];
			ActiveCircleInd = 0;
			this.ImageCat = new ImageCategorizationData();
			SearchText="";
		}
		/// <summary>
		/// Constructs a new object parser data object.
		/// </summary>
		/// <param name="imagename">string  The name of the image file (with extension).</param>
		public ObjectParserData(string imagename, string displayImagename, IFormParse owner)
		{
			Owner = owner;
			string[] stemp = imagename.Split(new char[] {'.'});
			Projname = "";
			for(int i=0; i<stemp.Length-2; i++)
				Projname = Projname + stemp[i];
			Imagename = imagename;
			DisplayImagename = displayImagename;
			Init2();
			if(Rand==null)
				Rand = new Random();
			Circles = new Circle[0];
			ActiveCircleInd = 0;

			this.ImageCat = new ImageCategorizationData();
			SearchText="";
			Location="";
			Gps="";
			CapturedAt = DateTime.MinValue;
		}
        #region Serialization (saving and opening files)
        /// <summary>
        /// If a file is an older version that requires edge merging when opened, then this will be false on the deserialization callback, true otherwise.
        /// </summary>
        public bool EdgesMerged = true;
        public ObjectParserData(SerializationInfo info, StreamingContext context)
        {	//	info.Get____
#if DebugDeserialization
            DbgMsg = "Begin ObjectParserData";
#endif
            Owner = CurrentlyActive;
            Projname = info.GetString("Projname");
            Imagename = info.GetString("Imagename");
            DisplayImagename = info.GetString("DisplayImagename");
            ObjCurrColor = (Color)info.GetValue("ObjCurrColor", typeof(Color));
            ObjOtherColor = (Color)info.GetValue("ObjOtherColor", typeof(Color));
            ObjNonColor = (Color)info.GetValue("ObjNonColor", typeof(Color));
            PolyLineColor = (Color)info.GetValue("PolyLineColor", typeof(Color));
            ObjectCt = info.GetInt32("ObjectCt");
            RootCategory = (CategoryNode)info.GetValue("RootCategory", typeof(CategoryNode));
            try { activeCategory = (CategoryNode)info.GetValue("activeCategory", typeof(CategoryNode)); }
            catch { activeCategory = RootCategory; }
            try { Circles = (Circle[])info.GetValue("Circles", typeof(Circle[])); }
            catch { Circles = new Circle[0]; }
            try { ActiveCircleInd = info.GetInt32("ActiveCircleInd"); }
            catch { ActiveCircleInd = 0; }
            try { Fstop = info.GetSingle("Fstop"); }
            catch { Fstop = -1.0f; }
            try { ShutterRate = info.GetSingle("ShutterRate"); }
            catch { ShutterRate = -1.0f; }
            try { IsoFilmSpeed = info.GetSingle("IsoFilmSpeed"); }
            catch { IsoFilmSpeed = 400.0f; }
            try { SearchText = info.GetString("SearchText"); }
            catch { SearchText = ""; }
            try { ImageCat = (ImageCategorizationData)info.GetValue("ImageCat", typeof(ImageCategorizationData)); }
            catch { ImageCat = new ImageCategorizationData(); }
            //	Store the image width and height.
            try
            {
                ImageWidth = info.GetInt32("ImageWidth");
                ImageHeight = info.GetInt32("ImageHeight");
            }
            catch
            {
                string oldImageName = Imagename;
                string oldDisplayImagename = DisplayImagename;
                this.AbsolutizeImageNames();
                try
                {
                    Bitmap bmp = new Bitmap(DisplayImagename);
                    ImageWidth = bmp.Width;
                    ImageHeight = bmp.Height;
                }
                catch
                {
                    ImageWidth = -1;
                    ImageHeight = -1;
                }
            }
            try { Location = info.GetString("Location"); }
            catch { Location = ""; }
            try { Gps = info.GetString("Gps"); }
            catch { Gps = ""; }
            try { CapturedAt = info.GetDateTime("CapturedAt"); }
            catch { CapturedAt = DateTime.MinValue; }
            try { SerializationCt = info.GetUInt64("SerializationCt"); }
            catch { SerializationCt = 0ul; }
            try { EdgesMerged = info.GetBoolean("EdgesMerged"); }
            catch { EdgesMerged = false; }

            if (SearchText == null)
                SearchText = "";

            if (Rand == null)
                Rand = new Random();

#if DebugDeserialization
            DbgMsg = "End ObjectParserData";
#endif
        }
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {	//	info.AddValue
            info.AddValue("Projname", Projname);
            info.AddValue("Imagename", Imagename);
            info.AddValue("DisplayImagename", DisplayImagename);
            info.AddValue("ObjCurrColor", ObjCurrColor);
            info.AddValue("ObjOtherColor", ObjOtherColor);
            info.AddValue("ObjNonColor", ObjNonColor);
            info.AddValue("PolyLineColor", PolyLineColor);
            info.AddValue("ObjectCt", ObjectCt);
            info.AddValue("RootCategory", RootCategory);
            info.AddValue("activeCategory", activeCategory);
            info.AddValue("Circles", Circles);
            info.AddValue("ActiveCircleInd", ActiveCircleInd);
            info.AddValue("Fstop", Fstop);
            info.AddValue("ShutterRate", ShutterRate);
            info.AddValue("IsoFilmSpeed", IsoFilmSpeed);
            info.AddValue("SearchText", SearchText);
            info.AddValue("ImageCat", ImageCat);
            info.AddValue("ImageWidth", ImageWidth);
            info.AddValue("ImageHeight", ImageHeight);
            info.AddValue("Location", Location);
            info.AddValue("Gps", Gps);
            info.AddValue("SerializationCt", SerializationCt);
            info.AddValue("CapturedAt", CapturedAt);
            info.AddValue("EdgesMerged", EdgesMerged);
        }
        public void OnDeserialization(object sender)
		{
			this.RootCategory.AssignOwnership(this);
		}
        /// <summary>
        /// This is called from within SerializeBinary or SerializeXml
        /// </summary>
        /// <param name="projname"></param>
        private void preSerialization()
        {
            //  These edges are merged, this is important for backwards compatibility with older versions of this software.
            EdgesMerged = true;

            //	Eliminate the redundant vertices.  This is executed because the software users sometimes lay multiple vertices on top of each other.
            //  It would be smarter to handle this while the polygons are being defined, but this statement attempts to correct the problem before saving.
            RootCategory.EliminateRedundantVertices();

            //	Remove empty polygons and objects.
            RootCategory.RemoveEmptyPolygonsAndObjects();

            //  Eliminate redundant owned edges.  Probably only useful for saves made before the ObjectParser.Command layer was created.
            RootCategory.EliminateRedundantOwnedEdges();

            //  Find any bad occlusion bridges and get rid of them.
            OcclusionBridgeList obl = new OcclusionBridgeList();
            RootCategory.ValidateBridges(true,obl);

            //	Store the image width and height
            if(Owner != null)
            {
                int width = Owner.GetDisplayImageWidth();
                int height = Owner.GetDisplayImageHeight();
                if(width>-1 && height>-1)
                {
                    ImageWidth = width;
                    ImageHeight = height;
                }
            }
            //	Increment the running count of serializations.
            SerializationCt++;
        }
		/// <summary>
		/// The method that saves an ObjectParserData file.
		/// </summary>
		/// <param name="projname">string  The file path and name.</param>
		public void Serialize(string projname, bool refreshPixelTaggers, SerializationType stype)
		{
            if (stype == SerializationType.Unknown)
                throw new ArgumentException("Unknown serialization type.");
            //  Prepare for serialization
            preSerialization();
			//	Reset pixel taggers before saving.
			if(refreshPixelTaggers)
				RootCategory.ResetPixelTagger();
			Projname = projname;
			//	Relativize the image names with respect to the stored path.
			RelativizeImageNames();

            if (stype == SerializationType.Xml)
            {
                System.Xml.XmlTextWriter xtw = new System.Xml.XmlTextWriter(projname,null);
                this.WriteXml(xtw);
                xtw.Close();
            }
            else
            {
                IFormatter formatter = null;
                if (stype == SerializationType.Binary)
                    formatter = new BinaryFormatter();
                else if (stype == SerializationType.Soap)
                    formatter = new SoapFormatter();
                else
                    throw new ApplicationException("Unrecognized serialization type.");
                Stream stream = new FileStream(projname, FileMode.Create, FileAccess.Write, FileShare.None);
                formatter.Serialize(stream, this);
                stream.Close();
            }
			
            //  Absolutize image names to be specific to this instance.
			AbsolutizeImageNames();
		}
        /// <summary>
		/// The method that opens an ObjectParserData object from a file.
		/// </summary>
		/// <param name="filepath">The filepath where the file exists.</param>
        /// <param name="owner">An interface to FormParse.</param>
		/// <returns></returns>
        public static ObjectParserData Deserialize(string filepath, IFormParse owner)
        {
            return Deserialize(filepath, owner, ObjectParserData.StypeFromFname(filepath));
        }
		/// <summary>
		/// The method that opens an ObjectParserData object from a file.
		/// </summary>
		/// <param name="filepath">The filepath where the file exists.</param>
        /// <param name="owner">An interface to FormParse.</param>
        /// <param name="stype">The serialization type format of the file.</param>
		/// <returns></returns>
		public static ObjectParserData Deserialize(string filepath, IFormParse owner, SerializationType stype)
		{
            CurrentlyActive = owner;

            if (stype == SerializationType.Unknown)
                throw new ArgumentException("Filename must end with a valid extension to determine which serialization format is valid.");
            if (stype == SerializationType.Xml)
            {
                /*
                System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(ObjectParserData));
                Stream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read);
                ObjectParserData obj = (ObjectParserData)ser.Deserialize(stream);
                stream.Close();
                */
                ObjectParserData obj = new ObjectParserData();
                System.Xml.XmlTextReader reader = new System.Xml.XmlTextReader(filepath);
                reader.Read();
                obj.ReadXml(reader);
                reader.Close();
                obj.EdgesMerged = true;
                obj.LastSerializationType = stype;
                obj.Projname = filepath;
                obj.AbsolutizeImageNames();
                obj.RootCategory.AssignFormParse(owner);
                obj.RootCategory.AssignOwnership(obj);
                obj.Owner = owner;
                return obj;
            }
            else
            {
                IFormatter formatter = null;
                if (stype == SerializationType.Binary)
                    formatter = new BinaryFormatter();
                else if (stype == SerializationType.Soap)
                    formatter = new SoapFormatter();

                Stream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read);
                ObjectParserData obj = (ObjectParserData)formatter.Deserialize(stream);
                stream.Close();
                obj.LastSerializationType = stype;
                obj.Projname = filepath;
                obj.AbsolutizeImageNames();
                obj.RootCategory.AssignFormParse(owner);
                obj.Owner = owner;
                return obj;
            }
        }
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            /*
            throw new Exception("The method or operation is not implemented.");
            System.Xml.Schema.XmlSchema output = new System.Xml.Schema.XmlSchema();
            System.Xml.Schema.XmlSchemaElement el;

            XmlSchemaComplexType vf = new XmlSchemaComplexType();
            vf.Name = "Vertex";

            el = new System.Xml.Schema.XmlSchemaElement();
            el.Name = "VertexF";
            el.ElementSchemaType = XmlSchemaType.GetBuiltInComplexType("Vertex");
            */
            return null;
        }
        /// <summary>
        /// This is typically called in support of ObjectParserData.ReadXml.
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="vertices"></param>
        public static void ReadVerticesFromXml(System.Xml.XmlReader reader,List<PolygonF> polygons,List<VertexF> vertices)
        {
            int depth = reader.Depth;
            VertexF vf = null;
            VertexF vf2;

            while(reader.Read() && reader.Depth>depth)
            {
                switch(reader.Name)
                {
                    case "Vertex":
                        {
                            vf = new VertexF();
                            vf.ReadXml(reader);
                            if(vertices.Count > vf.SerializationPointer)
                            {
                                //  the vertices are being deserialized out of order (with respect to their Id field).
                                //  update the vertices list.
                                if(vertices[vf.SerializationPointer] == null)
                                    vertices[vf.SerializationPointer] = vf;
                                else
                                {
                                    //  a dummy vertex occupies the list at location vf.SerializationPointer.
                                    //  the dummy may be pointed to by other objects in the deserialization process, so don't replace it with 'vf'.
                                    //  instead, assign proper values to the dummy.
                                    vf2 = vertices[vf.SerializationPointer];
                                    vf2.X = vf.X;
                                    vf2.Y = vf.Y;
                                    vf2.SerializationPointer = vf.SerializationPointer;
                                }
                            }
                            else
                            {
                                //  grow the list of vertices to the proper length
                                while(vertices.Count <= vf.SerializationPointer)
                                    vertices.Add(null);
                                //  assign the proper element to the deserialized vertex
                                vertices[vf.SerializationPointer] = vf;
                            }
                        } break;
                    default:
                        {
                            throw new Exception("Unrecognized field name in XML inside ObjectParserData.Vertices:  " + reader.Name);
                        }
                }
            }
        }
        /// <summary>
        /// This is typically called in support of ObjectParserData.ReadXml.
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="vertices"></param>
        public static void ReadPolygonsFromXml(System.Xml.XmlReader reader,List<PolygonF> polygons,List<VertexF> vertices)
        {
            int i;
            int depth = reader.Depth;
            PolygonF poly;
            PolygonF poly2 = null;

            bool reading=false;
            while( (reading || reader.Read()) && reader.Depth>depth)
            {
                reading = false;
                if((reader.NodeType == System.Xml.XmlNodeType.Element || reader.NodeType==System.Xml.XmlNodeType.Text) && reader.Name!="")
                {
                    reading = true;
                    switch(reader.Name)
                    {
                        case "Polygon":
                            {
                                poly = new PolygonF();
                                poly.ReadXml(reader);
                                if(poly.Contours==null)
                                    poly.Contours = new ContourFarray(true);
                                if(polygons.Count > poly.SerializationPointer)
                                {
                                    //  the polygons are being deserialized out of order (with respect to their Id field).
                                    //  update the polygon list.
                                    if(polygons[poly.SerializationPointer] == null)
                                        polygons[poly.SerializationPointer] = poly;
                                    else
                                    {
                                        //  a dummy polygon occupies the list at location poly.SerializationPointer.
                                        //  the dummy may be pointed to by other objects in the deserialization process, so don't replace it with 'poly'.
                                        //  instead, assign proper values to the dummy.
                                        poly2.StandardizesAutomatically = false;
                                        poly2 = polygons[poly.SerializationPointer];
                                        poly2.ActiveContourInd = poly.ActiveContourInd;
                                        poly2.Contours = poly.Contours;
                                        poly2.DbCache_MaskFileName = poly.DbCache_MaskFileName;
                                        poly2.DbCache_UniqueId = poly.DbCache_UniqueId;
                                        poly2.OwnedEdges = poly.OwnedEdges;
                                        poly2.Swallowed = poly.Swallowed;
                                        poly2.Clear();
                                        for(i=0;i<poly.Count;i++)
                                            poly2.Add(poly[i]);
                                        poly2.SerializationPointer = poly.SerializationPointer;
                                        poly2.StandardizesAutomatically = true;
                                    }
                                }
                                else
                                {
                                    //  grow the list of polygons to the proper length
                                    while(polygons.Count <= poly.SerializationPointer)
                                        polygons.Add(null);
                                    //  assign the proper element to this deserialized poly
                                    polygons[poly.SerializationPointer] = poly;
                                }
                            } break;
                        default:
                            {
                                throw new Exception("Unrecognized field name in XML inside ObjectParserData.Polygons:  " + reader.Name);
                            }
                    }
                }
            }
        }
        /// <summary>
        /// This is typically called in support of ObjectParserData.ReadXml.
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static Circle[] ReadCirclesFromXml(System.Xml.XmlReader reader)
        {
            int depth = reader.Depth;
            Circle cir;
            List<Circle> circles = new List<Circle>();
            while(reader.Read() && reader.Depth>depth)
            {
                switch(reader.Name)
                {
                    case "Circle":
                        {
                            cir = new Circle();
                            cir.ReadXml(reader);
                            circles.Add(cir);
                        } break;
                    default:
                        {
                            throw new Exception("Unrecognized field name in XML inside ObjectParserData.Circles:  " + reader.Name);
                        }
                }
            }
            return circles.ToArray();
        }
        /// <summary>
        /// The address to a CategoryNode, Object, Polygon, or Contour is a path delimited by '/' characters.  The polygon and contour names are integers beginning at zero.
        /// The first category must be called Root, so "address" will begin with "Root/".
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        public object FromAddress(string address)
        {
            string[] levels = address.ToUpper().Split('/','\\');
            if(levels[0] != "ROOT")
                return null;
            CategoryNode cn = this.RootCategory;
            ParsedObject po = null;
            PolygonF poly = null;
            ContourF con = null;
            bool found;
            int j;
            for(int i=1;i<levels.Length;i++)
            {
                if(levels[i].Length>0)
                {
                    if(po==null)  //  Still traversing through categories.
                    {
                        found = false;
                        for(j=0;j<cn.Sub.Count;j++)
                        {
                            if(cn.Sub[j].Name.ToUpper() == levels[i])
                            {
                                found = true;
                                cn = cn.Sub[j];
                                break;
                            }
                        }
                        if(!found)  //  Search for an object
                        {
                            found = false;
                            for(j=0;j<cn.Objects.Count;j++)
                            {
                                if(cn.Objects[j].Name.ToUpper()==levels[i])
                                {
                                    found = true;
                                    po = cn.Objects[j];
                                    break;
                                }
                            }
                        }
                        if(!found)
                            return cn;
                    }
                    else if(poly==null)
                    {
                        int ind = 0;
                        try
                        {
                            ind = Int32.Parse(levels[i]);
                        }
                        catch { }
                        if(po.Polygons.Count > ind)
                            poly = po.Polygons[ind];
                    }
                    else
                    {
                        int ind = 0;
                        try
                        {
                            ind = Int32.Parse(levels[i]);
                        }
                        catch { }
                        if(poly.Contours.Count > ind)
                            con = poly.Contours[ind];
                    }
                }
            }
            if(con!=null)
                return con;
            if(poly!=null)
                return poly;
            if(po!=null)
                return po;
            return cn;
        }
        /// <summary>
        /// The reader should already have started reading the "ObjectParserData" element.
        /// </summary>
        /// <param name="reader"></param>
        public void ReadXml(System.Xml.XmlReader reader)
        {

            this.RootCategory = null;

            List<VertexF> vertices = new List<VertexF>();
            List<PolygonF> polygons = new List<PolygonF>();
            string activeAddress = "";

            int depth = reader.Depth;
            bool reading=false;
            while((reading || reader.Read()) && reader.Depth>depth)
            {
                reading = false;
                if((reader.NodeType == System.Xml.XmlNodeType.Element || reader.NodeType==System.Xml.XmlNodeType.Text) && reader.Name!="")
                {
                    reading = true;
                    switch(reader.Name)
                    {
                        case "ObjectParserData":
                            {
                                throw new Exception("The ObjectParserData.ReadXml method encountered an ObjectParserData field inside its own scope."
                                + "  This probably means that the reader was no initialized to the right depth.");
                            } break;
                        case "Vertices":
                            {
                                ReadVerticesFromXml(reader,polygons,vertices);
                            } break;
                        case "Polygons":
                            {
                                ReadPolygonsFromXml(reader,polygons,vertices);
                            } break;
                        case "Circles":
                            {
                                this.Circles = ReadCirclesFromXml(reader);
                            } break;
                        case "Category":
                            {
                                this.RootCategory = new CategoryNode();
                                this.RootCategory.ReadXml(reader);
                            } break;
                        case "Projname":
                            {
                                this.Projname = reader.ReadElementContentAsString();
                            } break;
                        case "DisplayImagename":
                            {
                                this.DisplayImagename = reader.ReadElementContentAsString();
                            } break;
                        case "Imagename":
                            {
                                this.Imagename = reader.ReadElementContentAsString();
                            } break;
                        case "ImageHeight":
                            {
                                this.ImageHeight = reader.ReadElementContentAsInt();
                            } break;
                        case "ImageWidth":
                            {
                                this.ImageWidth = reader.ReadElementContentAsInt();
                            } break;
                        case "CapturedAt":
                            {
                                this.CapturedAt = DateTime.Parse(reader.ReadElementContentAsString());
                            } break;
                        case "Gps":
                            {
                                this.Gps = reader.ReadElementContentAsString();
                            } break;
                        case "Location":
                            {
                                this.Location = reader.ReadElementContentAsString();
                            } break;
                        case "SearchText":
                            {
                                this.SearchText = reader.ReadElementContentAsString();
                            } break;
                        case "Fstop":
                            {
                                this.Fstop = reader.ReadElementContentAsFloat();
                            } break;
                        case "IsoFilmSpeed":
                            {
                                this.IsoFilmSpeed = reader.ReadElementContentAsFloat();
                            } break;
                        case "ShutterRate":
                            {
                                this.ShutterRate = reader.ReadElementContentAsFloat();
                            } break;
                        case "ImageCategorizationData":
                            {
                                this.ImageCat = new ImageCategorizationData();
                                this.ImageCat.ReadXml(reader);
                            } break;
                        case "Application_ObjectParser":
                            {
                                // do nothing, read on into the contents of this element, as if its contents were at the same depth.
                                // use subsequent switch statemtns to handle subsequent elements.
                                reading = false;
                            } break;
                        case "ActiveCircleInd":
                            {
                                this.ActiveCircleInd = reader.ReadElementContentAsInt();
                            } break;
                        case "ObjectCt":
                            {
                                this.ObjectCt = reader.ReadElementContentAsInt();
                            } break;
                        case "ObjCurrColor":
                            {
                                this.ObjCurrColor = Color.FromArgb(reader.ReadElementContentAsInt());
                            } break;
                        case "ObjOtherColor":
                            {
                                this.ObjOtherColor = Color.FromArgb(reader.ReadElementContentAsInt());
                            } break;
                        case "ObjNonColor":
                            {
                                this.ObjNonColor = Color.FromArgb(reader.ReadElementContentAsInt());
                            } break;
                        case "PolyLineColor":
                            {
                                this.PolyLineColor = Color.FromArgb(reader.ReadElementContentAsInt());
                            } break;
                        case "PolyPtColor":
                            {
                                reader.ReadElementContentAsInt();
                            } break;
                        case "ActiveAddress":
                            {
                                activeAddress = reader.ReadElementContentAsString();
                            } break;
                        default:
                            {
                                throw new Exception("Unrecognized field name in XML inside ObjectParserData:  " + reader.Name);
                            }
                    }
                }
            }
            if(this.RootCategory!=null)
            {
                this.RootCategory.ReplaceDummyVariables(true,polygons,vertices);
                this.RootCategory.ResetPixelTagger();
                object thing = this.FromAddress(activeAddress);
                if(thing!=null)
                    ActiveThing = thing;
            }
        }
        public void WriteXml(System.Xml.XmlWriter writer)
        {
            int i;

            List<VertexF> vertices = new List<VertexF>();
            List<PolygonF> polygons = new List<PolygonF>();
            RootCategory.AssignNullSerializationPointers(true);
            RootCategory.EnumerateForSerialization(true, vertices, polygons);

            writer.WriteStartElement("ObjectParserData","http://www.cps.utexas.edu/Data/ObjectParser/Xml/ObjectParserData_Schema_1_1.xsd");
            //writer.WriteStartElement("ObjectParserData","file:///E:/2007_01_18_Objects/ObjectLib/ObjectParserData.xsd");

            writer.WriteStartElement("Vertices");
            for (i = 0; i < vertices.Count; i++)
                vertices[i].WriteXml(writer);
            writer.WriteEndElement();

            writer.WriteStartElement("Polygons");
            for (i = 0; i < polygons.Count; i++)
                polygons[i].WriteXml(writer);
            writer.WriteEndElement();

            writer.WriteStartElement("Circles");
            for(i=0; i<Circles.Length; i++)
                Circles[i].WriteXml(writer);
            writer.WriteEndElement();

            RootCategory.WriteXml(writer);

            writer.WriteElementString("Projname", Projname);
            writer.WriteElementString("DisplayImagename", DisplayImagename);
            writer.WriteElementString("Imagename", Imagename);
            writer.WriteElementString("ImageHeight", ImageHeight.ToString());
            writer.WriteElementString("ImageWidth", ImageWidth.ToString());

            writer.WriteElementString("CapturedAt",CapturedAt.ToString());
            writer.WriteElementString("Gps",Gps);
            writer.WriteElementString("Location",Location);
            writer.WriteElementString("SearchText", SearchText);

            writer.WriteElementString("Fstop", Fstop.ToString());
            writer.WriteElementString("IsoFilmSpeed", IsoFilmSpeed.ToString());
            writer.WriteElementString("ShutterRate", ShutterRate.ToString());

            this.ImageCat.WriteXml(writer);

            writer.WriteStartElement("Application_ObjectParser");
            writer.WriteElementString("ActiveCircleInd",ActiveCircleInd.ToString());

            object obj = this.ActiveThing;
            if(obj==null)
                writer.WriteElementString("ActiveAddress","");
            else
            {
                if(obj.GetType()==typeof(CategoryNode))
                    writer.WriteElementString("ActiveAddress",((CategoryNode)obj).Path);
                else if(obj.GetType()==typeof(ParsedObject))
                    writer.WriteElementString("ActiveAddress",((ParsedObject)obj).Address);
                else if(obj.GetType()==typeof(PolygonF))
                    writer.WriteElementString("ActiveAddress",((PolygonF)obj).Address);
                else if(obj.GetType()==typeof(ContourF))
                    writer.WriteElementString("ActiveAddress",((ContourF)obj).Address);
                else
                    writer.WriteElementString("ActiveAddress","");
            }

            writer.WriteElementString("ObjectCt", ObjectCt.ToString());
            writer.WriteElementString("ObjCurrColor",ObjCurrColor.ToArgb().ToString());
            writer.WriteElementString("ObjOtherColor",ObjOtherColor.ToArgb().ToString());
            writer.WriteElementString("ObjNonColor",ObjNonColor.ToArgb().ToString());
            writer.WriteElementString("PolyLineColor",PolyLineColor.ToArgb().ToString());
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
        /// <summary>
        /// Given a file name, returns the associated serializationType.
        /// </summary>
        /// <param name="fname"></param>
        /// <returns></returns>
        public static SerializationType StypeFromFname(string fname)
        {
            fname = fname.ToUpper();
            if (fname.EndsWith(".OBJPARSER.XML"))
                return SerializationType.Xml;
            if (fname.EndsWith(".OBJPARSER.SOAP"))
                return SerializationType.Soap;
            else if (fname.EndsWith(".OBJPARSER"))
                return SerializationType.Binary;
            return SerializationType.Unknown;
        }
        #endregion
        /// <summary>
		/// Initialization support.
		/// </summary>
		public void Init2()
		{
			// Initialize the nodes.
			RootCategory = new CategoryNode("Root", null, this);
			activeCategory = RootCategory;
			
			//	Initialize colors to default values.
			ObjCurrColor = Color.FromArgb(50,255,255,0);
			ObjOtherColor = Color.FromArgb(50,255,0,0);
			ObjNonColor = Color.FromArgb(50,0,0,255);
			PolyLineColor = Color.FromArgb(255,255,255,255);
		}
		[NonSerialized]
		public IFormParse Owner;
		/// <summary>
		/// Finds all polygons under the scope of the specified CategoryNode that contain the specified point.
		/// </summary>
		/// <param name="x">float  The point's x-coordinate.</param>
		/// <param name="y">float  The point's y-coordinate.</param>
		/// <param name="cn">CategoryNode  The category to search.</param>
		/// <param name="includeActivePoly">bool  True if Opd.ActivePoly should be included in the search.</param>
		/// <param name="output">PolygonFlist  All PolygonF objects to be returned are appended onto this ArrayList.</param>
		public void PolyContainingPoint(float x, float y, CategoryNode cn, bool includeActivePoly, PolygonFlist output)
		{
			PolygonF pf;
			PolygonF act = ActivePoly;
			//	Handle the active polygon.
			if(includeActivePoly)
			{
				if(act != null)
					if(act.InPolyNotSwallowed(x,y) > 0)
						output.Add(act);
			}
			//	Handle all Objects.
			for(int i=0; i<cn.Objects.Count; i++)
				for(int j=0; j<((ParsedObject)cn.Objects[i]).Polygons.Count; j++)
				{
					pf = ((ParsedObject)cn.Objects[i]).Polygons[j];
					if(act!=pf)
						if(pf.InPolyNotSwallowed(x,y) > 0)
						{
							output.Add(pf);
							break;
						}
				}
			//	Recursively handle sub-categories.
			for(int i=0; i<cn.Sub.Count; i++)
				PolyContainingPoint(x, y, (CategoryNode)cn.Sub[i], false, output);
		}
		/// <summary>
		/// Returns the list of polygons containing a point.
		/// </summary>
		/// <param name="x"></param>
		/// <param name="y"></param>
		/// <param name="includeActivePoly"></param>
		/// <returns></returns>
		public PolygonFlist PolysContainingPoint(float x, float y, bool includeActivePoly)
		{
			CategoryNode cn = ActiveCategory;
			CategoryNode pex = ActiveCategory.PixExcludeNode;
			if(pex != null)
				cn = pex;
			PolygonFlist output = new PolygonFlist();
			PolyContainingPoint(x,y,cn,includeActivePoly,output);
			return output;
		}
		/// <summary>
		/// Removes a VertexF from the active polygon.  Also removes the active polygon from vRemove.
		/// </summary>
		/// <param name="vRemove">VertexF</param>
		public void RemoveFromActivePoly(VertexF vRemove)
		{
			PolygonF pf = ActivePoly;
			if(pf != null)
			{
				pf.Remove(vRemove);
				vRemove.RemoveOwner(pf);
			}
		}
		/// <summary>
		/// Converts the image name to an absolute path.
		/// </summary>
		public void AbsolutizeImageNames()
		{
			Imagename = FileRelativizor.Absolutize(Projname, Imagename);
			if(DisplayImagename != null && DisplayImagename.Length>0)
				DisplayImagename = FileRelativizor.Absolutize(Projname, DisplayImagename);
		}
		/// <summary>
		/// Converts the image name to a path relative to the project file name.
		/// </summary>
		public void RelativizeImageNames()
		{
			Imagename = FileRelativizor.Relativize(Projname, Imagename);
            if(DisplayImagename != null && DisplayImagename.Length>0)
				DisplayImagename = FileRelativizor.Relativize(Projname, DisplayImagename);
		}
		/// <summary>
		/// Returns a list of the polygons that either have any vertex inside of the Rectangle rect OR where any vertex of the rect is inside of the polygon.
		/// </summary>
		/// <param name="rect"></param>
		/// <returns></returns>
		public PolygonFlist PolygonsInRegion(PolygonF region)
		{
			PolygonFlist output = new PolygonFlist();
			CategoryNode cn = ActiveCategory.PixExcludeNode;
			if(cn!=null)
				cn.PolygonsInRegion(region, output);
			
			return output;
		}
		/// <summary>
		/// Finds unstable polygons and returns them in a PolygonFlist.
		/// </summary>
		/// <param name="changedRegion">PolygonF  defines the changed region.</param>
		/// <returns>Any unstable polygons, otherwise, null.</returns>
		public PolygonFlist RegionIsStable(PolygonF changedRegion)
		{
			CategoryNode cn = ActiveCategory;
			if(cn.PixExcludeNode != null)
			{
				//	Inventory the set of polygons that are in the changedRegion.
				PolygonFlist polys = this.PolygonsInRegion(changedRegion);

				//	for each polygon i, for each other polygon j, if a vertex from i is in j AND it is not accounted for by swallowing
				//	add both polygons to the list of bad polygons (if they are not already part of the list).
				bool[] unstable = new bool[polys.Count];
				int unstableCt = 0;
				for(int i=0; i<polys.Count-1; i++)
				{
					for(int j=i+1; j<polys.Count; j++)
					{
						//	if not accounted for by swallowing
						if( polys[i].SwallowedBy!=polys[j] && polys[j].SwallowedBy!=polys[i] )
						{
							//	if either is not already registered as unstable
							if( !unstable[i] || !unstable[j] )
							{
								if( PolygonF.Overlaps(polys[i], polys[j]) )
								{
									if(!unstable[i])	unstableCt++;
									if(!unstable[j])	unstableCt++;
									unstable[i] = unstable[j] = true;
								}
							}
						}
					}
				}
				PolygonFlist output = new PolygonFlist();
				for(int i=0; i<polys.Count; i++)
					if(unstable[i])
						output.Add(polys[i]);
				return output;
			}
			return null;
		}
		/// <summary>
		/// The number of objects.
		/// </summary>
		public int NumObjects
		{
			get
			{
				return this.RootCategory.NumObjects;
			}
		}
		/// <summary>
		/// The number of non-residue objects.
		/// </summary>
		public int NumObjectsNotResidue
		{
			get
			{
				return this.RootCategory.NumObjectsNotResidue;
			}
		}
		/// <summary>
		/// Enters all the parsed objects into the list provided.
		/// </summary>
		/// <param name="list"></param>
		public void InventoryParsedObjects(ParsedObjectList list)
		{
			RootCategory.InventoryParsedObjects(list);
		}
		/// <summary>
		/// Attaches the attributes listed in the attributeFile to the ObjParser files in the opdFileDirectory.
		/// The log is an ArrayList of strings that tells you which lines in the attributeFile were matched to an ObjParser file,
		/// which lines were not matched, and which ObjParser files in the directory were not matched.
		/// </summary>
		/// <param name="files">string[]  The list of ObjParser files to load attributes for.</param>
		/// <param name="attributeFile">string  The text file that contains the attributes.</param>
		/// <param name="log">Each element is a string that begins with either the word Match, Unmatched File, or Unmatched Attribute.
		/// This is followed by a tab character and then the name of the file (without the path) or the column1 entry in the attribute file.  For
		/// elements where a match occurred, there is another tab character and then the word Done if the update was done.</param>
		public static void LoadAttributes(string[] files, string attributeFile, out ArrayList log)
		{
			log = new ArrayList();

			int i,j;

			if(!File.Exists(attributeFile))
				throw new ArgumentException("File does not exist:  "+attributeFile, "attributeFile");

			//	Read in the attributes (split each line by its tab spaces);
			ArrayList attributes = new ArrayList();
			StreamReader sr = File.OpenText(attributeFile);
			string str = sr.ReadLine();;
			do
			{
				attributes.Add(str.Split('\t'));
				str = sr.ReadLine();
			} while(str != null);
			sr.Close();

			//	Match each file with an attribute by moving it from the attributes ArrayList into the log ArrayList;
			//	Label as an Unmatched File if an attribute is not found.
			for(i=0; i<files.Length; i++)
			{
				files[i] = files[i].ToUpper();
				j=0;
				while(j<attributes.Count)
				{
					str = ((string[])attributes[j])[0].ToUpper();
					if(files[i].IndexOf(str)>=0)
					{
						log.Add(attributes[j]);
						attributes.RemoveAt(j);
						break;
					}
					j++;
				}
				if(j>=attributes.Count)
					log.Add("Unmatched File\t" + files[i]);
			}

			//	If the attributes ArrayList contains anymore attributes, then they are Unmatched Attributes.  Record that now.
			for(i=0; i<attributes.Count; i++)
				log.Add("Unmatched Attribute\t" + ((string[])attributes[i])[0]);

			//	Clear the attributes ArrayList, then move the attributes back in (with null placeholders for Unmatched Files).
			attributes.Clear();
			for(i=0; i<files.Length; i++)
			{
				if(log[i].GetType()==typeof(string[]))
				{
					attributes.Add(log[i]);
					log[i] = "Match\t" + ((string[])attributes[i])[0];
				}
				else
					attributes.Add(null);
			}

			//	Load each data file and put the attrbutes inside, then save it.
			for(i=0; i<files.Length; i++)
			{
				if(attributes[i] != null)
				{
					//	Backup the old file.
					File.Copy(files[i], files[i]+".Old", true);
					//	Prepare the variable of file data.
					ObjectParserData opd = null;
					//	Open the file.
					try
					{
						opd = ObjectParserData.Deserialize(files[i], null);
					}
					catch
					{
						opd = null;
						log[i] = ((string)log[i]) + "\tERROR, File may be corrupt:  " + files[i];
					}
					//	Get the data into the new file.
					if(opd != null)
					{
						string[] attr = (string[])attributes[i];
						if(attr.Length<8)
							log[i] = ((string)log[i]) + "\tERROR, Not enough attributes.";
						else
						{
							string status = "";
							try
							{
								status = "Encoding shutter rate (column 1)";
								if(attr[1].ToUpper() != "NULL")
									opd.ShutterRate = Convert.ToSingle(attr[1]);

								status = "Encoding fstop (column 2)";
								if(attr[2].ToUpper() != "NULL")
									opd.Fstop = Convert.ToSingle(attr[2]);

								status = "Encoding GPS";
								if(attr[3].ToUpper() != "NULL")
									opd.Gps = attr[3];

								status = "Encoding Location";
								if(attr[4].ToUpper() != "NULL")
									opd.Location = attr[4];

								status = "Encoding date and time";
								str = "";
								if(attr[5].ToUpper() != "NULL")
									str = attr[5];
								if(attr[6].ToUpper() != "NULL")
									str = str + " " + attr[6];
								if(str != "")
									opd.CapturedAt = DateTime.Parse(str);
								
								status = "Encoding descriptors.";
								if(attr[7].ToUpper() != "NULL")
									opd.SearchText = attr[7];

								status = "Resaving file.";

								opd.Serialize(opd.Projname, false, StypeFromFname(opd.Projname));

								log[i] = ((string)log[i]) + "\tDone";
							}
							catch(Exception ex)
							{
								log[i] = ((string)log[i]) + "\tERROR "+status+".  Exception="+ex.Message;
							}
						}
					}
				}
			}
		}
		/// <summary>
		/// Restores busted edges and may recalculate edges for each affected PolygonF.
		/// </summary>
		/// <param name="fepa">The list of EdgeGroup objects that determine which busted
		/// edges should be restored.</param>
		/// <param name="recalcEdges">If true, the edges of the affected polygons are recalculated
		/// using PolygonF.CalcEdges().</param>
		public void RestoreEdges(EdgeGroupArray fepa, bool recalcEdges)
		{
			if(fepa!=null)
			{
				Edge fe;
				int i;
				//	Restore busted edges
				for(i=0;i<fepa.Count;i++)
				{
					fe = fepa[i].DominantEdge;
					if(fe!=null)
						fe.Owner.OwnedEdges.Add(fe);
				}
				//	Recalculate edges
				if(recalcEdges)
				{
					for(i=0;i<fepa.Count;i++)
					{
						fe = fepa[i].DominantEdge;
						if(fe!=null)
							fe.Owner.CalcEdges();
					}
				}
			}
		}
		/// <summary>
		/// Restores bridges into each list[i].Pf1.Owner.Bridges list.
		/// </summary>
		/// <param name="list">The list of occlusion bridges to be restored.</param>
		public void RestoreBridges(OcclusionBridgeList list)
		{
			if(list!=null)
				for(int i=0; i<list.Count; i++)
					list[i].Pf1.Owner.Bridges.Add(list[i]);
		}
		/// <summary>
		/// Calculates the edge pairs and makes sure that there are no extra owned edges for each parsed object.
		/// </summary>
		public void TidyOwnedEdges()
		{
			RootCategory.CalcEdgeGroups();
			RootCategory.TidyOwnedEdges();
		}
		/// <summary>
		/// Converts this instance to a matlab structure of the given variable name.  The variable 'temp' in the matlab workspace is cleared.
		/// </summary>
		/// <param name="variableName">Contains everything appended before the fieldnames of the structure.  For example, "struct_name(1)."</param>
		/// <param name="matlab"></param>
		public void ToMatlabStruct(string variableName, EngMATLib.EngMATAccess mat)
		{
			int i;
			//	Project name
			mat.EvaluateAsString(variableName + "ProjectName='" + this.ShortProjname + "';");
			//	Circle
			for(i=0; i<Circles.Length; i++)
				Circles[i].ToMatlabStruct(variableName + "Circle(" + (i+1).ToString() + ").", mat);
			//	CaptureDateTime
			mat.EvaluateAsString(variableName + "CaptureDateTime=[" +
				this.CapturedAt.Year.ToString() + ", " + 
				this.CapturedAt.Month.ToString() + ", " + 
				this.CapturedAt.Day.ToString() + ", " + 
				this.CapturedAt.Hour.ToString() + ", " + 
				this.CapturedAt.Minute.ToString() + ", " + 
				this.CapturedAt.Second.ToString() + "];");
			//	Gps
			mat.EvaluateAsString(variableName + "Gps='" + this.Gps.Replace("'", "`") + "';");
			//	Location
			mat.EvaluateAsString(variableName + "Location='" + this.Location + "';");
			//	Categorization data
			this.ImageCat.ToMatlabStruct(variableName + "Cat.", mat);
			//	Image dimensions
			mat.EvaluateAsString(variableName + "ImageSize=[" + this.ImageWidth.ToString() + ", " + this.ImageHeight.ToString() + "];");
			//	Camera settings
			mat.EvaluateAsString(variableName + "Fstop=" + this.Fstop.ToString() + ";");
			mat.EvaluateAsString(variableName + "IsoFilmSpeed=" + this.IsoFilmSpeed.ToString() + ";");
			mat.EvaluateAsString(variableName + "ShutterRate=" + this.ShutterRate.ToString() + ";");
			mat.EvaluateAsString(variableName + "RgbScalar=" + this.PixelFactor.ToString() + ";");
			//	Search text
			mat.EvaluateAsString(variableName + "SearchText='" + this.SearchText + "';");
			//	Append each category
			this.RootCategory.ToMatlabStruct(variableName + "Root.", mat);
		}
        public void AddCircle(Circle cir)
        {
            Circle[] cirs = new Circle[Circles.Length+1];
            for(int i=0;i<Circles.Length;i++)
                cirs[i] = Circles[i];
            cirs[cirs.Length-1] = cir;
            Circles = cirs;
        }
        public void RemoveCircle(Circle cir)
        {
            Circle[] cirs = new Circle[Circles.Length-1];
            int ct=0;
            for(int i=0;i<Circles.Length;i++)
            {
                if(ct>=cirs.Length && Circles[i]!=cir)
                {
                    cirs = Circles;
                    break;
                }
                if(Circles[i]!=cir)
                    cirs[ct++] = Circles[i];
            }
            Circles = cirs;
        }
    }
    public enum SerializationType
    {
        Binary,
        Soap,
        Xml,
        Unknown
    }
}